#pragma once
#include<iostream>
#include<functional>
#include<mutex>

namespace myptr{

	template <class T>
	class auto_ptr {
	public:
		auto_ptr(T* ptr = nullptr):_ptr(ptr)
		{ }

		auto_ptr(auto_ptr<T>& a) 
		{
			_ptr = a.relese();
		}
		T* get()
		{
			return _ptr;
		}

		T* relese()
		{
			T* ret = _ptr;
			_ptr = nullptr;
			return ret;
		}

		void reset(T* ptr = nullptr)
		{
			delete _ptr;
			_ptr = ptr;
		}

		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
		auto_ptr<T>& operator= (auto_ptr<T>& p)
		{
			_ptr = p.relese();
			return *this;
		}
		~auto_ptr()
		{
			if(_ptr != nullptr)
			delete _ptr;
		}
	private:
		T* _ptr;
	};



	template<class T>
	struct default_delete {
		void operator()(T* ptr)
		{
			delete ptr;
		}
	};


	template<class T>
	struct default_delete<T[]> {
		void operator()(T* ptr)
		{
			delete[] ptr;
		}
	};
	
	template <class T, class D = default_delete<T>>
	class unique_ptr;


	template <class T, class D >
	class unique_ptr {
	public:
		unique_ptr(T* ptr = nullptr, D del = D()) :_ptr(ptr),_del(del)
		{}

		unique_ptr(unique_ptr&& a):_ptr(a._ptr),_del(move(a._del))
		{
		}
		unique_ptr& operator= (unique_ptr&& a)
		{
			if (&a != this)
			{
				reset(a.release());
				_del = move(a._del);
			}
			return *this;
		}

		unique_ptr(unique_ptr& a) = delete;
		
		T* get()
		{
			return _ptr;
		}

		void swap(unique_ptr& a)
		{
			swap(_ptr, a._ptr);
			swap(_del, a._del);
		}
		explicit operator bool()const  
		{
			return _ptr != nullptr;
		}

		T* relese()
		{
			T* ret = _ptr;
			_ptr = nullptr;
			return ret;
		}

		void reset(T* ptr = nullptr)
		{
			del(_ptr);
			_ptr = ptr;
		}

		T& operator*() const
		{
			return *_ptr;
		}

		T* operator->() const 
		{
			return _ptr;
		}
		T& operator[](int pos)const
		{
			return _ptr[pos];
		}
		unique_ptr<T>& operator= (unique_ptr<T>& p) = delete;
		~unique_ptr()
		{
			if (_ptr != nullptr)
				_del(_ptr);
		}
	private:
		T* _ptr;
		D _del;
	};


	template <class T, class D>
	class unique_ptr<T[], D> {
	public:
		unique_ptr(T* ptr,D del = D() ) :_ptr(ptr), _del(del)
		{
		}
		unique_ptr(unique_ptr&& a) :_ptr(a._ptr), _del(move(a._del))
		{
			a._ptr = nullptr;
		}
		unique_ptr(unique_ptr& a) = delete;

		T* get() const
		{
			return _ptr;
		}

		explicit operator bool()const
		{
			return _ptr != nullptr;
		}

		unique_ptr& operator= (unique_ptr&& a) {
			if (&a != this)
			{
				reset(a.release());
				_del = move(a._del);
			}
			return *this;
		}

		void swap(unique_ptr& a)
		{
			std::swap(_ptr, a._ptr);
			std::swap(_del, a._del);
		}

		T* release()
		{
			T* ret = _ptr;
			_ptr = nullptr;
			return ret;
		}

		void reset(T* ptr = nullptr)
		{
			if(_ptr != nullptr)
			_del(_ptr);
			_ptr = ptr;
		}

		T& operator[](int pos)const
		{
			return _ptr[pos];
		}
		unique_ptr<T>& operator= (unique_ptr<T>& p) = delete;

		~unique_ptr()
		{
			if(_ptr != nullptr)
			_del(_ptr);
		}
	private:
		T* _ptr;
		D _del;
	};


	template <class T>
	class shared_ptr {
	public:
		shared_ptr(T* ptr = nullptr) :_ptr(ptr), _size(new int{ 1 }), _del([](T* ptr) {delete ptr; }), _mutex(new std::mutex)
		{
		}

		template< class D >
		shared_ptr(T* ptr , D del) :_ptr(ptr),_del(del), _size(new int{1}), _mutex(new std::mutex)
		{}
		
		void addref()
		{
			std::lock_guard<std::mutex> lock(*_mutex);

			(*_size)++;

		}

		void release()
		{
			std::lock_guard<std::mutex> lock(*_mutex);
				if ( _ptr && --(*_size) == 0 )
				{
					_del(_ptr);
					delete _size;
				}
		}
		shared_ptr(shared_ptr& a):_ptr(a._ptr),_size(a._size),_del(a._del),_mutex(a._mutex)
		{
			addref();
		}
		shared_ptr(shared_ptr&& a) noexcept
		{
			swap(a);
		}
		explicit operator bool()const
		{
			return _ptr != nullptr;
		}


		T* get()
		{
			return _ptr;
		}


		void reset(T* ptr = nullptr)
		{
			*this = shared_ptr<T>(ptr);
		}

		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}

		void swap(shared_ptr& p)
		{
			std::swap(_ptr, p._ptr);
			std::swap(_size, p._size);
			std::swap(_del, p._del);
		}

		bool unique() const
		{
			std::lock_guard<std::mutex> lock(*_mutex);
			return *_size == 1;
		}
		T& operator[](int pos)
		{
			return _ptr[pos];
		}
		int use_count() const
		{
			std::lock_guard<std::mutex> lock(*_mutex);
			return *_size;
		}

		shared_ptr& operator= (shared_ptr& p)
		{
			if (_ptr != p._ptr)
			{
				release();
				_ptr = p._ptr;
				_size = p._size;
				_del = p._del;
				_mutex = p._mutex;
				addref();
			}
			return *this;
		}

		shared_ptr& operator= (shared_ptr&& p)
		{
			if (_ptr != p._ptr)
			{
				swap(p);
			}
			return *this;
		}
		~shared_ptr()
		{
			release();
		}
	private:
		T* _ptr;
		int* _size;
		std::function<void(T*)> _del;
		std::mutex* _mutex;
	};


};
